import Cesium, { ArcType, Cartesian3, GeometryInstance } from '../Ces/Cesium';

interface ODLineOptions {
  positions: Cartesian3[];
  lineWidth?: number;
  segment?: number;
  repeat?: number;
  arcType?: ArcType;
  speed?: number;
  reverse?: boolean;
  rotateXY?: boolean;
  clampToGround?: boolean;
  url: string;
  delay?: number;
}

class ODLine {
  private _primitive?: any
  private _image?: HTMLImageElement;
  private _positions: Cartesian3[];
  private _lineWidth: number;
  private _segment: number;
  private _repeat: number;
  private _arcType: ArcType;
  private _speed: number;
  private _reverse: boolean;
  private _rotateXY: boolean;
  private _clampToGround: boolean;
  private _delay: number;

  constructor(options: ODLineOptions) {
    this._positions = options.positions;
    this._lineWidth = options.lineWidth?? 2.0
    this._segment = options.segment?? 2
    this._repeat = options.repeat?? 4
    this._arcType = options.arcType??Cesium.ArcType.GEODESIC
    this._speed = options.speed??1.0
    this._reverse = options.reverse??false
    this._rotateXY = options.rotateXY?? false
    this._clampToGround = options.clampToGround?? false
    this._delay = options.delay?? 0.0

    Cesium.Resource.fetchImage(options.url).then((image) => {
      this._image = image;
    });
  }

  get repeat(): number {
    return this._repeat;
  }

  set repeat(value: number) {
    this._repeat = value;
  }

  get speed(): number {
    return this._speed;
  }

  set speed(value: number) {
    this._speed = value;
  }

  get delay(): number {
    return this._delay;
  }

  set delay(value: number) {
    this._delay = value;
  }

  get reverse(): boolean {
    return this._reverse;
  }

  set reverse(value: boolean) {
    this._reverse = value;
  }

  isDestroyed(): boolean {
    return false;
  }

  destroy(): void {
    if (this._primitive) {
      this._primitive.destroy();
      this._primitive = undefined;
    }
  }

  update(frameState): void {
    if (frameState.passes.render) {
      if (this._image) {
        const image = this._image;
        this._image = undefined;
        this.create(frameState.context, image);
      }
      if (this._primitive) {
        this._primitive.update(frameState);
      }
    }
  }

  private createInstances(stCartesian: Cartesian3, edCartesian: Cartesian3): GeometryInstance {
    const addPointCartesian = Cesium.Cartesian3.add(stCartesian, edCartesian, new Cesium.Cartesian3());
    const positions: Cartesian3[] = [];
    const segment = this._segment;

    if (segment < 2) {
      positions.push(stCartesian);
      positions.push(edCartesian);
    } else {
      const midPointCartesian = Cesium.Cartesian3.divideByScalar(addPointCartesian, 2, new Cesium.Cartesian3());
      const midPointCartographic = Cesium.Cartographic.fromCartesian(midPointCartesian);

      midPointCartographic.height = Cesium.Cartesian3.distance(stCartesian, edCartesian) / 5;
      const midPoint = Cesium.Ellipsoid.WGS84.cartographicToCartesian(midPointCartographic, new Cesium.Cartesian3());

      const spline = new Cesium.CatmullRomSpline({
        times: [0.0, 0.5, 1.0],
        points: [stCartesian, midPoint, edCartesian],
      });

      for (let i = 0, len = segment; i < len + 1; i++) {
        positions.push(spline.evaluate(i / len));
      }
    }

    const geometry = this._clampToGround
      ? new Cesium.GroundPolylineGeometry({
        positions,
        width: this._lineWidth,
        arcType: this._arcType,
      })
      : new Cesium.PolylineGeometry({
        positions,
        width: this._lineWidth,
        arcType: this._arcType,
      });

    return new Cesium.GeometryInstance({
      geometry,
    });
  }

  private create(context, image: HTMLImageElement): void {
    const texture = new Cesium.Texture({
      context,
      source: image,
    });

    const material = new Cesium.Material({
      fabric: {
        uniforms: {
          image: texture,
          repeat: this.repeat,
          speed: this.speed,
          reverse: this.reverse,
          rotateXY: this._rotateXY,
          delay: this._delay,
        },
        source: shaderMovingTexture,
      },
    });

    const appearance = new Cesium.PolylineMaterialAppearance({
      material,
    });

    appearance.material._uniforms.speed_2 = () => this.speed;
    appearance.material._uniforms.reverse_3 = () => this.reverse;

    const geometryInstances: GeometryInstance[] = [];
    const positions = this._positions;

    for (let i = 0; i < positions.length; i += 2) {
      geometryInstances.push(this.createInstances(positions[i], positions[i + 1]));
    }

    this._primitive = this._clampToGround
      ? new Cesium.GroundPolylinePrimitive({
        geometryInstances,
        asynchronous: false,
        appearance,
      })
      : new Cesium.Primitive({
        geometryInstances,
        asynchronous: false,
        appearance,
      });
  }
}

const shaderMovingTexture = `czm_material czm_getMaterial(czm_materialInput materialInput) {
    czm_material material = czm_getDefaultMaterial(materialInput);
    float iTime = (czm_frameNumber / 60.0) * speed;
    vec2 st = materialInput.st;

    if(reverse){
        st.x = 1.0 - st.x;
    }

    st.x = fract(st.x - iTime  + delay);

    float scaledWidth = fract(repeat * st.x);
    scaledWidth = abs(scaledWidth - floor(scaledWidth));
    st.x = scaledWidth;

    vec2 uv = st;

    if(rotateXY){
        uv.x = st.y;
        uv.y = st.x;
    }

    vec4 color = texture(image, uv);
    material.diffuse = color.rgb;
    material.alpha = color.a;
    return material;
}`;

export default ODLine;
